16. 反射&代理

类的加载

  1. 加载

  2. 连接

    2.1 校验:例如检查JDK 版本,检查字节码是否以 “魔数 cafe” 开头等。

    2.2 准备:给成员变量(类变量,静态变量)赋于默认值。把常量(final)等值在方法区的常量池中给准备好。

    2.3 解析:理解为把类中的类型名转换成该类型的 Class 对象的地址。例如,将 String 转换为 String 类型对应的 Class 地址。

  3. 初始化: <clinit>类初始化,<clinit>类初始化会执行两部分内容,分别是静态变量的显式赋值和静态代码块的内容。当一个类初始化时,如果发现它的父类没有被初始化,那么会先初始话父类。每一个类只会初始化一次, 所以是线程安全的(所以在单例中的饿汉式是线程安全的)。

注意⚠️:类的加载不一定会发生类的初始化。只是大部分类在加载的时候会发生类的初始化而已。 那么哪些操作时引发类的初始化呢?

  1. main 方法所在的类在加载时,直接就先初始话。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.itguigu.singleton;

    public class TestLoader {
    static {
    System.out.println("main 方法所在的类会先初始化");
    // 程序运行会输出 main 方法所在的类会先初始化
    }
    public static void main(String[] args) {

    }
    }
  2. new 一个类的对象,一定会先完成类的初始化。

  3. 调用该类的静态变量(final 的常量除外),或者静态方法。
  4. 使用 java.lang.reflect 包的方法对类进行反射调用。
  5. 当初始化一个类的时候,如果发现它的父类没有被初始化,那么会初始化它的父类。

哪些操作不会引起类的初始化

  1. 当引用静态的常量(final )时,不会引起类的初始化。原因是常量在类加载的连接中的准备阶段就已经在方法区的常量池中给准备好了。

  2. 当访问一个静态域时,只有真正声明这个域的类才会被初始化。换句话说,通过子类访问父类的静态域时(静态的东西),只会初始化父类,不会初始化子类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.itguigu.singleton;

    public class TestLoader {
    public static void main(String[] args) {
    System.out.println(Son.number);
    // 输出内容为 父类静态代码块 1。
    // 通过子类访问父类的静态域时(静态的东西),只会初始化父类,不会初始化子类。
    }
    }

    class Father{
    public static int number = 1;
    static {
    System.out.println("父类静态代码块");
    }
    }

    class Son extends Father{
    static {
    System.out.println("子类静态代码块");
    }
    }
  3. 通过数组定义类引用,不会触发此类的初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.itguigu.singleton;

    public class TestLoader {
    public static void main(String[] args) {
    MyClass[] myClasses = new MyClass[6];
    // 只是创建了数组对象,但是没有 MyClass 的对象。
    // 数组对象的类型为 MyClass[]
    // 数组元素的类型为 MyClass
    }
    }

    class MyClass{}

获取 Class 对象

  1. 类型名.class。注意:基本数据类型和 void 只能通过这种方式获取。
  2. 对象.getClass()。获取到的是对象的运行时类型,只能用于引用数据类型,不能是基本数据类型,基本数据类型是不能 new 对象的。
  3. Class.forName(“类的全名称”)。只能用于引用数据类型。
  4. 类加载器对象.loadClass(“类的全名称”)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.itguigu.singleton;

import org.junit.Test;

public class testClass {
@Test
public void test1() {
Class class1 = int.class;
Class class2 = Void.class;
Class class3 = String.class;
Class class4 = Object.class;
Class class5 = Comparable.class;

System.out.println(class1); // int
System.out.println(class2); // class java.lang.Void
System.out.println(class3); // class java.lang.String
System.out.println(class4); // class java.lang.Object
System.out.println(class5); // interface java.lang.Comparable
}

@Test
public void test2() {
String string = "zhangsan";
Class class1 = string.getClass();
Integer integer = 1;
Class class2 = integer.getClass();

System.out.println(class1); // class java.lang.String
System.out.println(class2); // class java.lang.Integer
}

@Test
public void test3() throws ClassNotFoundException {
Class class1 = Class.forName("java.lang.String");
Class class2 = Class.forName("java.lang.Comparable");

System.out.println(class1); // class java.lang.String
System.out.println(class2); // interface java.lang.Comparable
}

@Test
public void test4() throws ClassNotFoundException {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class class1 = systemClassLoader.loadClass("java.lang.String");

System.out.println(class1); // class java.lang.String
}
}

类加载器

类加载器是负责加载类的对象

每个 Class 对象都包含一个对定义它的 ClassLoader 的引用,即通过 Class 对象可以得到加载它的类加载对象。

类加载器的分类

  1. 引导类加载器(Bootstrap Classloader)又称为根类加载器,负责加载 Jaca 核心库。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.itguigu.singleton;

    import org.junit.Test;

    public class TestClassLoader {
    @Test
    public void test1() {
    Class class1 = String.class;
    ClassLoader classLoader = class1.getClassLoader();
    System.out.println(classLoader);
    // 输出结果为 null
    // 因为 String 的原生代码是由 c或者c++ 实现的,并不继承自 java.lang.ClassLoader, 所以这里为 null
    }
    }
  2. 扩展类加载器(Extension ClassLoader)主要负责加载 JAVA_HOME/jre/ext/*.jar。下的 jar 包文件。

  3. 应用程序类加载器(Application ClassLoader)负责加载 classpath 下的(也就是自己实现的)类。

  4. 自定义类加载器。例如 tomcat。一班字节码需要加解密的时候需要用到自定义类加载器,还有就是加载特定目录下的类的时候会用到。

4 认为 3 是它的父加载器,3 会认为2 是它的父加载器,一直网上。 Java 的类加载的过程是一个双亲(parent)委托模式加载的。举例如下:当 “应用程序类加载器” 接收到一个加载任务时,首先会去内存搜索是否已经加载过,如果没有那么就将任务提交给它的父加载器,父加载器收到任务时,也是重复上面的步骤,直到找到根加载器。如果根加载器还没有找到,那么就将任务回复回传,直到又回到了 “应用程序类加载器” ,如果此时都还没有找到,那么就抛出异常。

类加载器加载资源文件

类加载器的作用

  1. 最主要的作用是加载类
  2. 辅助作用是加载类路径下的资源文件,例如加载 src 下的文件

例子1:使用 ClassLoader 加载 src 下面的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.itguigu.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

import org.junit.Test;

public class TestLoadFile {
/**
* 加载 src 下面的文件
* @throws IOException
*/
@Test
public void test1() throws IOException {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("test.properties"));
System.out.println(properties);
}

/**
* 加载 src 外面的文件
* @throws IOException
* @throws FileNotFoundException
*/
@Test
public void test2() throws FileNotFoundException, IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("test_out.properties"));
System.out.println(properties);
}
}

在 Java SE 中使用 ClassLoader.getSystemResourceAsStream 加载 src 下面的文件是没有问题的,因为用到的是应用程序类加载器去加载的文件。而这种方法在 Java EE 阶段就会有问题,因为 EE 阶段的类路径在 WEB-INF/classes 下,必须由自定义类加载器去加载。下面这种方法 SE 和 EE 都适用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itguigu.singleton;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.junit.Test;

public class TestLoadFile {
/**
* 加载 src 下面的文件,适用于 Java SE 和 Java EE 阶段
* @throws IOException
*/
@Test
public void test1() throws IOException {
Properties properties = new Properties();
// 1. 先获取当前类的加载器对象
ClassLoader classLoader = TestLoadFile.class.getClassLoader();

// 2. 利用当前类的加载器对象加载文件
InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
properties.load(resourceAsStream);
System.out.println(properties);
}
}

例子2:需要加载的文件在 src 下面的包中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itguigu.singleton;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.junit.Test;

public class TestLoadFile {
/**
* 加载 src 下面包中的文件
* @throws IOException
*/
@Test
public void test1() throws IOException {
Properties properties = new Properties();
// 1. 先获取当前类的加载器对象
ClassLoader classLoader = TestLoadFile.class.getClassLoader();

// 2. 利用当前类的加载器对象加载文件
InputStream resourceAsStream = classLoader.getResourceAsStream("com/itguigu/singleton/test_in.properties");
properties.load(resourceAsStream);
System.out.println(properties);
}
}

例子3:新建一个 Source Floder ,加载里面的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.itguigu.singleton;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.junit.Test;

public class TestLoadFile {
/**
* 加载 source folder 下的文件
* config 文件夹是一个 source folder,等价于 src
* @throws IOException
*/
@Test
public void test1() throws IOException {
Properties properties = new Properties();
// 1. 先获取当前类的加载器对象
ClassLoader classLoader = TestLoadFile.class.getClassLoader();

// 2. 利用当前类的加载器对象加载文件
InputStream resourceAsStream = classLoader.getResourceAsStream("config.properties");
properties.load(resourceAsStream);
System.out.println(properties);
}
}

反射

在运行时可以获取类的信息,对类进行操作,而且该类可能是在编译时完全未知的类型。使 Java 具有动态语言的特性。

反射的作用

1. 在运行期间动态的获取某个类的详细信息
Class 类的 API
  1. Package getPackage(),获取包信息
  2. int getModifiers(),获取类的修饰符
  3. getName(),获取类名称
  4. getSupperclass(),获取父类
  5. getInterfaces(),获取父接口,返回的是 Class数组。
  6. Field[] getFields(),获得某个类的所有的公共(public)的字段,包括父类中的字段。
  7. Field getField(String name),根据名称,获得某个类的某个的公共(public)的字段,可以是父类中的字段
  8. Field[] getDeclaredFields(),获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
  9. Field getDeclaredField(),获取指定的字段,包括public、private和proteced,但是不包括父类的申明字段。
  10. Constructor<?>[] getConstructors(),获得某个类的所有的公共(public)的构造器,包括父类中的。
  11. Constructor getConstructor(Class<?>… parameterTypes),根据参数类型获得某个类的某个的公共(public)的构造器,可以父类中的。
  12. Constructor<?> [] getDeclaredConstructors(),获取某个类的所有构造器,包括public、private和proteced,但是不包括父类的申明的。
  13. Constructor getDeclaredConstructor(Class<?>… parameterTypes) ,根据参数类型获取某个类的某个构造器,包括public、private和proteced,但是不包括父类的申明的。
  14. Method[] getMethods()
  15. Method getMethod(String name, Class<?>… parameterTypes)
  16. Method[] getDeclaredMethods()
  17. Method getDeclaredMethods(String name, Class<?>… parameterTypes)
java.lang.refect
  1. Package 类型

    getName(),获取包名称

  2. Modifier 类型

    调用 toString 能获取到字符串格式的修饰符

  3. Field 类型

    Field 代表属性,每一个 Field 的对象代表一个类中的某一个属性。可以调用 getModifiers 获取属性的修饰符,getType 获取属性的类型,getName 获取属性的名称。

  4. Constructor 类型

    Constructor 代表构造器,可以调用 getModifiers 获取修饰符,getName 获取名称。getParameterTypes 获取行参列表

  5. Method 类型

    Method 代表方法,可以调用 getModifiers 获取修饰符,getName 获取名称。getReturnType 获取返回值类型。getParameterTypes 获取行参列表

2. 在运行期间动态的创建任意类型的对象
newInstance

使用 newInstance 的前提是这个类型必须要有无参构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itguigu.api;

import org.junit.Test;

// 在运行期动态的创建任意类型的对象,只要这个类型可以创建对象
public class TestNewInstance {
@Test
public void Test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 1.获取 Class 对象
Class<?> classzz = Class.forName("java.lang.String");
// 2.创建对象
Object object = classzz.newInstance();
System.out.println(object);
}
}
获取构载器,在创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itguigu.api;

import java.lang.reflect.Constructor;

import org.junit.Test;

// 在运行期动态的创建任意类型的对象,只要这个类型可以创建对象
public class TestNewInstance {
@Test
public void Test2() throws Exception {
// 1.获取 Class 对象
Class<?> classzz = Class.forName("java.lang.String");
// 2.根据需求传入不同的形参类型,获取构造器对象(相当于得到一个构载器,参数类型为 StringBuffer)
Constructor<?> constructor = classzz.getDeclaredConstructor(StringBuffer.class);
// 3.用构造器创建对象,并传入对应实参(用上面构载器创建对象,并传入对应类型参数)
Object object = constructor.newInstance(new StringBuffer("zhangsan"));
System.out.println(object);
}
}

上面两种方法各有优缺点,但是我们在创建类的时候,尽量保证类的无参构造。

3. 在运行期间动态的获取或修改属性的值

这也是数据库框架 MyBatis,Hibernate 将数据变成 Java Bean 的原理。

动态的获取或修改属性的值步骤如下:

  1. 获取 Class 对象
  2. 获取 Field 对象
  3. 根据 Class 对象创建实例对象
  4. 调用 Field对象.set(实例对象,属性值) 进行属性值的修改
  5. 调用 Field对象.get(实例对象) 进行属性值的获取

要值得注意的是,如果属性是私有的,那么调用 set 方法会出现访问错误,这时需要调用 Field对象.setAccessible 方法将对象设置为可访问。

在演示 “运行期间动态的获取或修改属性的值” 之前,我们创建了一个类,并将其打包成为了 jar 包存放在 /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext 目录下,类中只有一个私有的 name 属性,还有其他 get set 方法,有参和无参构造等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itguigu.api;


import java.lang.reflect.Field;

import org.junit.Test;

// 在运行期动态的获取对象属性的值和设置值
public class TestNewInstance {
@Test
public void Test2() throws Exception {
// 1.获取 Class 对象
Class<?> classzz = Class.forName("com.itguigu.ext.demo.Student");
// 2.获取Field属性对象
Field nameField = classzz.getDeclaredField("name");
// 3.创建 Student对象
Object student = classzz.newInstance();
nameField.setAccessible(true);
// 4.为name属性赋值
nameField.set(student, "张三");
// 5. 获取属性的值
Object fieldValue = nameField.get(student);
System.out.println(fieldValue); // 张三
}
}
4. 在运行期间动态的调用任意对象的任意方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.itguigu.api;


import java.lang.reflect.Method;

import org.junit.Test;

// 在运行期间动态的调用任意对象的任意方法
public class TestNewInstance {
@Test
public void Test2() throws Exception {
// 1.获取 Class 对象
Class<?> classzz = Class.forName("com.itguigu.ext.demo.Student");
// 2.通过方法名 + 形参列表 获取 Method
Method method = classzz.getDeclaredMethod("setName", String.class);
// 3.创建实例对象
Object student = classzz.newInstance();
// 4.调用方法
// 如果 method 代表的方法没有返回值,那么会返回 null
// 如果 method 代表的方式静态方法,那么就不需要创建实例对象,第一个参数传 null 即可。
method.invoke(student, "李四");
System.out.println(student);

// 调用 getName
Method method2 = classzz.getDeclaredMethod("getName");
Object value = method2.invoke(student);
System.out.println(value);
}
}
5. 获取泛型父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.itguigu.api;


import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.junit.Test;

// 在运行期间动态的调用任意对象的任意方法
public class TestNewInstance {
@Test
public void Test2() throws Exception {
// 1.获取 Class 对象
Class clazz = Son.class;

// 2.获取父类,没有泛型
/**
* Class superclass = clazz.getSuperclass();
* System.out.println(superclass); //class com.itguigu.api.Father
*/

// 2.获取泛型父类
Type genericSuperclass = clazz.getGenericSuperclass();

// 3.强转为参数化类型
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

// 获取泛型实参
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type);
/**
* class java.lang.String
* class java.lang.Integer
*/
}
}
}

abstract class Father<T, U>{}

class Son extends Father<String, Integer>{}
6. 获取注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.itguigu.api;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.junit.Test;

// 在运行期间动态的调用任意对象的任意方法
public class TestNewInstance {
@Test
public void Test2() throws Exception {
// 1.获取 Class 对象
Class<?> clazz = MyClass.class;
// 2.获取 MyClass 上的注解(这里注意,必须要为注解加上声明周期,否则获取不到 Class 对象)
MyAnntation annotation = clazz.getAnnotation(MyAnntation.class);
// 3. 获取注解上参数的值
String value = annotation.value();
System.out.println(value);
}
}

@MyAnntation(value = "张三")
class MyClass{

}

// 指定声明周期,这样在运行时才能获取到 Class 对象
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnntation{
String value(); // 为注解加上参数
}

代理

静态代理

让代理类替被代理类完成一些 “非业务” 代码,核心业务代码还是交给被代理者自己完成。代理模式都有三部分组成,分别是1,主题。2,被代理类。3,代理类。在静态代理中代理类和被代理类需要实现同样的主题接口(下面的 UserDaoProxy 实现了 UserDao 接口),还因为要把核心业务交还给被代理者自己完成,所有在代理类中还应该有被代理者对象的引用(private UserDao target)。

例如给所有的方法都增加一个功能,统计该方法的运行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.itguigu.proxy;

/**
* 代理模式由三部分组成。1,主题。2,被代理类。3,代理类
* @author rex
*
*/
public class TestProxy {
public static void main(String[] args) {
/**
* UserDaoImpl() 是被代理对象
* UserDaoProxy 是代理者对象
*/
UserDao userDao = new UserDaoProxy(new UserDaoImpl());
userDao.instert();
}
}

// 1 在代理模式中这属于主题
interface UserDao{
void instert();
void delete();
void update();
void search();
}

// 2 在代理模式中这属于被代理类
class UserDaoImpl implements UserDao{
/**
* 现在想统计每个方法的运行时间
*/
@Override
public void instert() {
System.out.println("insert 数据");
}
@Override
public void delete() {
System.out.println("delete 数据");
}
@Override
public void update() {
System.out.println("update 数据");
}
@Override
public void search() {
System.out.println("search 数据");
}
}

// 3 在代理模式中这属于代理类,他代理了 UserDaoImpl,并能在执行 UserDaoImpl 中方法前后做一些相关的操作
class UserDaoProxy implements UserDao{
private UserDao target;
public UserDaoProxy(UserDao target) {
super();
this.target = target;
}

@Override
public void instert() {
long start = System.currentTimeMillis();
target.instert();
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
@Override
public void delete() {
long start = System.currentTimeMillis();
target.delete();
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
@Override
public void update() {
long start = System.currentTimeMillis();
target.update();
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
@Override
public void search() {
long start = System.currentTimeMillis();
target.search();
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
}

静态代理的缺点就是不够灵活。需要些写很多重复代码。例如现在现在有一个 OrderDaoImpL 也需要为所有的方法时间计算时间的需求,那么代码就需要重新再实现一遍。

动态代理

动态代理和静态代理的区别在于代理类的实现不一样,动态代理的代理类是自动生成的。需要借助 java.lang.reflect.Proxy 类中的 newProxyInstance 方法,该方法的第一个参数是被代理者的类加载器,第二个参数是被代理者实现的接口们,第三个参数是替被代理者完成工作的处理器对象。newProxyInstance 的返回值就是代理类的对象(代理类是在内存中自动生成的)

动态代理中虽然不需要自己实现代理类,但是还需实现 “代理工作处理器”, 代理工作处理器需要实现 InvocationHandler 接口后需要重写 invoke 方法,invoke 方法是自动调用的。其中 invoke 方法的第一个参数 proxy 为代理类对象,第二个参数 method 为代理类要真正执行的方法(例如下面的 insert….),第三个参数 args 是给 method 方法的实参列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.itguigu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 代理模式由三部分组成。1,主题。2,被代理类。3,代理类
* @author rex
*
*/
public class TestProxy {
public static void main(String[] args) {
UserDaoImpl userDaoImpl = new UserDaoImpl();
ClassLoader loader = userDaoImpl.getClass().getClassLoader();

Class<?>[] interfaces = userDaoImpl.getClass().getInterfaces();

Hanlder h = new Hanlder(userDaoImpl);

// loader 是被代理者的类加载器
// interfaces 是被代理者的接口们
// h 是处理器对象
UserDao proxyInstance = (UserDao) Proxy.newProxyInstance(loader, interfaces, h);
proxyInstance.update();
/**
当前被代理的类为com.itguigu.proxy.$Proxy0
update 数据
当前方法:update消耗时间为:1
*/
}
}

// 1 在代理模式中这属于主题
interface UserDao{
void instert();
void delete();
void update();
void search();
}

// 2 在代理模式中这属于被代理类
class UserDaoImpl implements UserDao{
/**
* 现在想统计每个方法的运行时间
*/
@Override
public void instert() {
System.out.println("insert 数据");
}
@Override
public void delete() {
System.out.println("delete 数据");
}
@Override
public void update() {
System.out.println("update 数据");
}
@Override
public void search() {
System.out.println("search 数据");
}
}

// 3 实现 InvocationHandler 接口 (和静态代理相比,这里和调用方法不一样)
class Hanlder implements InvocationHandler{
private Object target;
public Hanlder(Object target) {
super();
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("当前被代理的类为" + proxy.getClass().getName());

// target 是被代理对象
Object returnValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("当前方法:" + method.getName() + "消耗时间为:" + (end - start));

return returnValue;
}
}